import java.awt.*;
import java.awt.event.*;
import java.awt.image.MemoryImageSource;
import java.io.File;
import java.io.IOException;
import java.nio.FloatBuffer;

import javax.imageio.ImageIO;
import javax.media.opengl.*;
import javax.media.opengl.glu.*;

import com.sun.opengl.util.*;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Iterator;

public class AntColonyTycoon extends Frame implements GLEventListener 
{
	static final long serialVersionUID = 7526471155622776147L;

	/*
 * **********************************************
 * *			Local variables					*
 * **********************************************
 */
	private GLCanvas canvas;
	private Robot robot;
	private Image curImage; 
	private Cursor canCur;
	private int screenWidth = 600, screenHeight = 600;		// Screen size.
	private ArrayList<VisibleObject> visibleObjects;		// A list of objects that will be displayed on screen.
	private Camera camera;									// The camera object.
	private World world; 										// The world.
	private Queen queen;
	private long previousTime = Calendar.getInstance().getTimeInMillis(); // Get current time.

	//screen property's
	private GraphicsEnvironment ge;
	private GraphicsDevice[] gs;
	private DisplayMode dm;
	
	//VBO vars
	private boolean VBOsupported;	
	
/*
 * **********************************************
 * *		Initialization methods				*
 * **********************************************
 */

	public AntColonyTycoon() 
	{
		// Make a new window.
		super("Ant Colony Tycoon");
		
		// Let's change the window to our liking.
		ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
		gs = ge.getScreenDevices();
		dm = gs[0].getDisplayMode();
		screenWidth = dm.getWidth();
		screenHeight = dm.getHeight();
		setSize(screenWidth,screenHeight);
		setSize( screenWidth, screenHeight);
		setBackground( Color.white );

		// The window also has to close when we want to.
		this.addWindowListener( new WindowAdapter()
		{
			public void windowClosing( WindowEvent e )
			{
				System.exit(0);
			}
		});
		//initialize everything
		initializeObjects();						// Initialization of all the objects.
		initJOGL();									// Initialize JOGL.
		
		// Set the frame to visible. This automatically calls upon OpenGL to prevent a blank screen.
		setVisible(true);
	}
	
	private void initJOGL()	
	{
		// First, we set up JOGL. We start with the default settings.
		GLCapabilities caps = new GLCapabilities();
		// Then we make sure that JOGL is hardware accelerated and uses double buffering.
		caps.setDoubleBuffered( true );
		caps.setHardwareAccelerated( true );
		//Enable anti-aliasing
		caps.setSampleBuffers(true); 
		caps.setNumSamples(8);

		// Now we add the canvas, where OpenGL will actually draw for us. We'll use settings we've just defined. 
		canvas = new GLCanvas( caps );
		add( canvas );
		/* We need to add a GLEventListener to interpret OpenGL events for us. Since MazeRunner implements
		 * GLEventListener, this means that we add the necesary init(), display(), displayChanged() and reshape()
		 * methods to this class.
		 * These will be called when we are ready to perform the OpenGL phases of MazeRunner. 
		 */
		canvas.addGLEventListener( this );
		
		// Cursor shouldn't be visible on canvas
		try 
		{
			curImage = ImageIO.read(new File("cursor.png"));
		} 
		catch (IOException e1)
		{
			// TODO Auto-generated catch block
			e1.printStackTrace();
		}
		canCur =
		        Toolkit.getDefaultToolkit().createCustomCursor
		             (curImage, new Point(0, 0), "crosshairCursor");
		canvas.setCursor(canCur);
		try
		{
			robot = new Robot();
		}
		catch(Exception e)
		{
			e.printStackTrace();
		}
		
		//detects mouse movement to make changes to the angle of the players view
		canvas.addMouseMotionListener(new MouseMotionAdapter()
		{
			@Override
			public void mouseMoved(MouseEvent e)
			{
				float dMx = screenWidth/2-e.getXOnScreen();
				float dMy = screenHeight/2-e.getYOnScreen();
				camera.setHorAngle(camera.getHorAngle()+dMx/10);
				camera.setVerAngle(camera.getVerAngle()+dMy/10);

				//uses robot, to move the mouse back to the middle of the screen
				//to prepare the mouse for the next movement event
				robot.mouseMove(screenWidth/2,screenHeight/2);
			}
		});
		canvas.addMouseWheelListener(new MouseWheelListener()
		{
			//when mousewheel is rolled
			public void mouseWheelMoved(MouseWheelEvent e)
			{
				camera.setSpeedY(e.getWheelRotation()*30);
			}
		});
		
		//detects mouse button presses and mouse exiting the window
		canvas.addMouseListener(new MouseAdapter()
		{
			public void mouseExited(MouseEvent e)
			{				
				//uses robot, to move the mouse back to the middle of the screen
				//whenever the mouse exits the window
				robot.mouseMove(screenWidth/2,screenHeight/2);				
			}
			//when a mouse button is pressed 
			public void mousePressed(MouseEvent e)
			{
				/**
				 *  calculate collision between view-vector and world-grid:
				 *  calculate if there is collision in x,z-plane
				 *  next calculate
				 *  calculate collision in y
				 *  if not proceed to next x,z-plane collission
				 */
				double cX,cY,cZ;
				double dx,dy,dz;

				dx = camera.getDx();
				dy = camera.getDy();
				dz = camera.getDz();

				double absDX = Math.abs(dx);
				double absDZ=Math.abs(dz);
				if(absDX>absDZ)
				{
					dz/=absDX;
					dy/=absDX;
					dx/=absDX;
				}
				else
				{					
					dx/=absDZ;
					dy/=absDZ;
					dz/=absDZ;
				}
				cX = camera.getLocationX();
				cY = camera.getLocationY();
				cZ = camera.getLocationZ();
				boolean hitfound=false;
				int x=-1,z=-1;

				double y=-1;
				/**
				while(!hitfound)//camera on grid looking at grid
				{
					try
					{
						for(int t = 0;t<(World.WORLD_SIZE*World.SQUARE_SIZE);t++)
						{
							x = World.getGrid(t*dx+cX);
							y = t*dy+cY;
							z = World.getGrid(t*dz+cZ);
							if(x>=World.WORLD_SIZE||x<0||z>=World.WORLD_SIZE||z<0)break;
							if(y<=World.HEIGHT_MAP[x][z])
							{
								hitfound = true;
								t=World.WORLD_SIZE*World.SQUARE_SIZE;
								world.click(x,z);
								queen.setTarget(x, z);
							}
						}
						break;
						}
					catch(Exception ex)
					{
						ex.printStackTrace();
						System.out.println("[dz-1]-x: "+x+", y: "+y+", z: "+z);
						System.exit(-1);
					}
				}
				*/
			}
			//when a mousebutton is released
			public void mouseReleased(MouseEvent e)
			{

			}
			
		});

		//Detects keyboard actions to control player movement and other functions
		canvas.addKeyListener(new KeyAdapter()
		{	
			@Override
			//detects when a key is pressed
			public void keyPressed( KeyEvent e)
			{
				switch(e.getKeyCode())
				{
					case 87: camera.setSpeedZ(-10);  break; //W pressed
					case 65: camera.setSpeedX(-10);  break; //A pressed
					case 83: camera.setSpeedZ(10); break;//S pressed
					case 68: camera.setSpeedX(10); break;//D pressed
				}
			}
			@Override
			//detects when a key is released
			public void keyReleased( KeyEvent e)
			{
				switch(e.getKeyCode())
				{
				case 27: break; //Esc released
				case 77: break; //M pressed
				}
			}
		});
		
		/* We need to create an internal thread that instructs OpenGL to continuously repaint itself.
		 * The Animator class handles that for JOGL.
		 */
		FPSAnimator anim = new FPSAnimator( canvas, 50 );
		anim.start();
	}
	
	private void initializeObjects()	
	{
		// We define an ArrayList of VisibleObjects to store all the objects that need to be
		// displayed by MazeRunner.
		visibleObjects = new ArrayList<VisibleObject>();
		// Add the world that we will be using.
		world = new World();
		world.setHeightmap();
		world.setColMap();
		visibleObjects.add( world );
		//queen = new Queen(25.5*World.SQUARE_SIZE,50,25.5*World.SQUARE_SIZE);
		//visibleObjects.add(queen);
		//Init camera
		camera = new Camera((400), 	// x-position
				 			(400),		// y-position
				 			(400), 	// z-position
				 			90, 0 ); //angles
	}

/*
 * **********************************************
 * *		OpenGL event handlers				*
 * **********************************************
 */
	public void init(GLAutoDrawable drawable)
	{
		drawable.setGL( new DebugGL(drawable.getGL() )); // We set the OpenGL pipeline to Debugging mode.
        GL gl = drawable.getGL();
        GLU glu = new GLU();
        
        VBOsupported = gl.isFunctionAvailable("glGenBuffers") &&
        gl.isFunctionAvailable("glBindBuffer") &&
        gl.isFunctionAvailable("glBufferData") &&
        gl.isFunctionAvailable("glDeleteBuffers");
        
        //create VBO's
        world.initGL(gl);
        
        gl.glClearColor(0, 0, 0.2f, 0);								// Set the background color.
        
        // Now we set up our viewpoint.
        gl.glMatrixMode( GL.GL_PROJECTION );						// We'll use orthogonal projection.
        gl.glLoadIdentity();										// Reset the current matrix.
        glu.gluPerspective( 60, screenWidth/screenHeight, 0.1, 1000);	// Set up the parameters for perspective viewing.
        gl.glMatrixMode( GL.GL_MODELVIEW );
        
        // Enable back-face culling.
        gl.glCullFace( GL.GL_BACK );
        gl.glEnable( GL.GL_CULL_FACE );
        
        // Enable Z-buffering.
        gl.glEnable( GL.GL_DEPTH_TEST );
		        
        // Set and enable the lighting.
        float lightPosition[] = { 0.0f, 600.0f, 0.0f, 1.0f }; 			// High up in the sky!
        float lightColour[] = { 1.0f, 1.0f, 1.0f, 0.0f };				// White light!
        gl.glLightfv( GL.GL_LIGHT0, GL.GL_POSITION, lightPosition, 0 );	// Note that we're setting Light0.
        gl.glLightfv( GL.GL_LIGHT0, GL.GL_AMBIENT, lightColour, 0);
        gl.glLightfv( GL.GL_LIGHT0, GL.GL_DIFFUSE, lightColour, 0);
        gl.glEnable( GL.GL_LIGHTING );
        gl.glEnable( GL.GL_LIGHT0 );
        
        // Set the shading model.
        gl.glShadeModel( GL.GL_SMOOTH );
	}
	
	public void display(GLAutoDrawable drawable) 
	{
		GL gl = drawable.getGL();
		GLU glu = new GLU();
		
		// Calculating time since last frame.
		Calendar now = Calendar.getInstance();		
		long currentTime = now.getTimeInMillis();
		long deltaTime = currentTime - previousTime;
		previousTime = currentTime;
		
		camera.calculateVRP();
		
		gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT );
		gl.glLoadIdentity();
        glu.gluLookAt( camera.getLocationX(), camera.getLocationY(), camera.getLocationZ(), 
 			   camera.getVrpX(), camera.getVrpY(), camera.getVrpZ(),
 			   camera.getVuvX(), camera.getVuvY(), camera.getVuvZ() );

        // Display all the visible objects of MazeRunner.
        for( Iterator<VisibleObject> it = visibleObjects.iterator(); it.hasNext(); ) {
        	it.next().display(gl);
        }

        gl.glLoadIdentity();
        // Flush the OpenGL buffer.
        gl.glFlush();
	}

	public void displayChanged(GLAutoDrawable drawable, boolean modeChanged, boolean deviceChanged)
	{
		// GL gl = drawable.getGL();
	}
	
	public void reshape(GLAutoDrawable drawable, int x, int y, int width, int height) 
	{
		GL gl = drawable.getGL();
		GLU glu = new GLU();
		
		// Setting the new screen size and adjusting the viewport.
		screenWidth = width;
		screenHeight = height;
		gl.glViewport( 0, 0, screenWidth, screenHeight );
		
		// Set the new projection matrix.
		gl.glMatrixMode( GL.GL_PROJECTION );
		gl.glLoadIdentity();
		
		gluPerspective(gl, 60, screenWidth/screenHeight, .05, 20000 );
		gl.glMatrixMode( GL.GL_MODELVIEW );
	}

/*
 * **********************************************
 * *				Methods						*
 * **********************************************
 */

private void gluPerspective(GL gl, double fovy, double aspect, double zNear, double zFar)
{
   double xmin, xmax, ymin, ymax;

   ymax = zNear * Math.tan(fovy * Math.PI / 360.0);
   ymin = -ymax;
   xmin = ymin * aspect;
   xmax = ymax * aspect;

   gl.glFrustum(xmin, xmax, ymin, ymax, zNear, zFar);
}

/*
 * **********************************************
 * *				  Main						*
 * **********************************************
 */
	public static void main(String[] args) 
	{
		// Create and run MazeRunner.
		new AntColonyTycoon();
	}
}